{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "20de56a8",
   "metadata": {},
   "source": [
    "# Prompting Claude for \"JSON Mode\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e7fe136",
   "metadata": {},
   "source": [
    "Claude doesn't have a formal \"JSON Mode\" with constrained sampling. But not to worry -- you can still get reliable JSON from Claude! This recipe will show you how."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5e1a230",
   "metadata": {},
   "source": [
    "First, let's look at Claude's default behavior."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0c07a2c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install anthropic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "fc39114b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import re\n",
    "from pprint import pprint\n",
    "\n",
    "from anthropic import Anthropic"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1b991340",
   "metadata": {},
   "outputs": [],
   "source": [
    "client = Anthropic()\n",
    "MODEL_NAME = \"claude-opus-4-1\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c28ca1ad",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Here is a JSON dictionary with names of famous athletes and their respective sports:\n",
      "\n",
      "{\n",
      "  \"athletes\": [\n",
      "    {\n",
      "      \"name\": \"Usain Bolt\",\n",
      "      \"sport\": \"Track and Field\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Michael Phelps\",\n",
      "      \"sport\": \"Swimming\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Serena Williams\",\n",
      "      \"sport\": \"Tennis\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"LeBron James\",\n",
      "      \"sport\": \"Basketball\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Lionel Messi\",\n",
      "      \"sport\": \"Soccer\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Simone Biles\",\n",
      "      \"sport\": \"Gymnastics\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Tom Brady\",\n",
      "      \"sport\": \"American Football\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Muhammad Ali\",\n",
      "      \"sport\": \"Boxing\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Nadia Comaneci\",\n",
      "      \"sport\": \"Gymnastics\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Michael Jordan\",\n",
      "      \"sport\": \"Basketball\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Pelé\",\n",
      "      \"sport\": \"Soccer\"\n",
      "    },\n",
      "    {\n",
      "      \"name\": \"Roger Federer\",\n",
      "      \"sport\": \"Tennis\"\n",
      "    }\n",
      "  ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "message = (\n",
    "    client.messages.create(\n",
    "        model=MODEL_NAME,\n",
    "        max_tokens=1024,\n",
    "        messages=[\n",
    "            {\n",
    "                \"role\": \"user\",\n",
    "                \"content\": \"Give me a JSON dict with names of famous athletes & their sports.\",\n",
    "            },\n",
    "        ],\n",
    "    )\n",
    "    .content[0]\n",
    "    .text\n",
    ")\n",
    "print(message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a63ad7e",
   "metadata": {},
   "source": [
    "Claude followed instructions and outputted a nice dictionary, which we can extract with code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "08626553",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'athletes': [{'name': 'Usain Bolt', 'sport': 'Track and Field'},\n",
       "  {'name': 'Michael Phelps', 'sport': 'Swimming'},\n",
       "  {'name': 'Serena Williams', 'sport': 'Tennis'},\n",
       "  {'name': 'LeBron James', 'sport': 'Basketball'},\n",
       "  {'name': 'Lionel Messi', 'sport': 'Soccer'},\n",
       "  {'name': 'Simone Biles', 'sport': 'Gymnastics'},\n",
       "  {'name': 'Tom Brady', 'sport': 'American Football'},\n",
       "  {'name': 'Muhammad Ali', 'sport': 'Boxing'},\n",
       "  {'name': 'Nadia Comaneci', 'sport': 'Gymnastics'},\n",
       "  {'name': 'Michael Jordan', 'sport': 'Basketball'},\n",
       "  {'name': 'Pelé', 'sport': 'Soccer'},\n",
       "  {'name': 'Roger Federer', 'sport': 'Tennis'}]}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def extract_json(response):\n",
    "    json_start = response.index(\"{\")\n",
    "    json_end = response.rfind(\"}\")\n",
    "    return json.loads(response[json_start : json_end + 1])\n",
    "\n",
    "\n",
    "extract_json(message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "39275885",
   "metadata": {},
   "source": [
    "But what if we want Claude to skip the preamble and go straight to the JSON? One simple way is to prefill Claude's response and include a \"{\" character."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "155e088a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "   \"athletes\":[\n",
      "      {\n",
      "         \"name\":\"Michael Jordan\",\n",
      "         \"sport\":\"Basketball\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Babe Ruth\",\n",
      "         \"sport\":\"Baseball\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Muhammad Ali\",\n",
      "         \"sport\":\"Boxing\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Serena Williams\",\n",
      "         \"sport\":\"Tennis\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Wayne Gretzky\",\n",
      "         \"sport\":\"Hockey\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Michael Phelps\",\n",
      "         \"sport\":\"Swimming\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Usain Bolt\",\n",
      "         \"sport\":\"Track and Field\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Mia Hamm\",\n",
      "         \"sport\":\"Soccer\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Michael Schumacher\",\n",
      "         \"sport\":\"Formula 1 Racing\"\n",
      "      },\n",
      "      {\n",
      "         \"name\":\"Simone Biles\",\n",
      "         \"sport\":\"Gymnastics\"\n",
      "      }\n",
      "   ]\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "message = (\n",
    "    client.messages.create(\n",
    "        model=MODEL_NAME,\n",
    "        max_tokens=1024,\n",
    "        messages=[\n",
    "            {\n",
    "                \"role\": \"user\",\n",
    "                \"content\": \"Give me a JSON dict with names of famous athletes & their sports.\",\n",
    "            },\n",
    "            {\"role\": \"assistant\", \"content\": \"Here is the JSON requested:\\n{\"},\n",
    "        ],\n",
    "    )\n",
    "    .content[0]\n",
    "    .text\n",
    ")\n",
    "print(message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64e94c65",
   "metadata": {},
   "source": [
    "Now all we have to do is add back the \"{\" that we prefilled and we can extract the JSON."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "6c066ac6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'athletes': [{'name': 'Michael Jordan', 'sport': 'Basketball'},\n",
       "  {'name': 'Babe Ruth', 'sport': 'Baseball'},\n",
       "  {'name': 'Muhammad Ali', 'sport': 'Boxing'},\n",
       "  {'name': 'Serena Williams', 'sport': 'Tennis'},\n",
       "  {'name': 'Wayne Gretzky', 'sport': 'Hockey'},\n",
       "  {'name': 'Michael Phelps', 'sport': 'Swimming'},\n",
       "  {'name': 'Usain Bolt', 'sport': 'Track and Field'},\n",
       "  {'name': 'Mia Hamm', 'sport': 'Soccer'},\n",
       "  {'name': 'Michael Schumacher', 'sport': 'Formula 1 Racing'},\n",
       "  {'name': 'Simone Biles', 'sport': 'Gymnastics'}]}"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "output_json = json.loads(\"{\" + message[: message.rfind(\"}\") + 1])\n",
    "output_json"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cd4492fd",
   "metadata": {},
   "source": [
    "For very long and complicated prompts, which contain multiple JSON outputs so that a string search for \"{\" and \"}\" don't do the trick, you can also have Claude output each JSON item in specified tags for future extraction."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "443ad932",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " \n",
      "\n",
      "<athlete_sports>\n",
      "{\n",
      "  \"Michael Jordan\": \"Basketball\",\n",
      "  \"Serena Williams\": \"Tennis\",\n",
      "  \"Lionel Messi\": \"Soccer\", \n",
      "  \"Usain Bolt\": \"Track and Field\",\n",
      "  \"Michael Phelps\": \"Swimming\"\n",
      "}\n",
      "</athlete_sports>\n",
      "\n",
      "<athlete_name>\n",
      "{\n",
      "  \"first\": [\"Magnificent\", \"Motivating\", \"Memorable\"],\n",
      "  \"last\": [\"Joyful\", \"Jumping\", \"Jocular\"]\n",
      "}\n",
      "</athlete_name>\n",
      "\n",
      "<athlete_name>\n",
      "{\n",
      "  \"first\": [\"Skillful\", \"Strong\", \"Superstar\"],\n",
      "  \"last\": [\"Winning\", \"Willful\", \"Wise\"]\n",
      "}\n",
      "</athlete_name>\n",
      "\n",
      "<athlete_name>\n",
      "{\n",
      "  \"first\": [\"Legendary\", \"Lively\", \"Leaping\"],\n",
      "  \"last\": [\"Magical\", \"Marvelous\", \"Masterful\"]  \n",
      "}\n",
      "</athlete_name>\n",
      "\n",
      "<athlete_name>\n",
      "{\n",
      "  \"first\": [\"Unbeatable\", \"Unbelievable\", \"Unstoppable\"],\n",
      "  \"last\": [\"Brave\", \"Bold\", \"Brilliant\"]\n",
      "}\n",
      "</athlete_name>\n",
      "\n",
      "<athlete_name>\n",
      "{\n",
      "  \"first\": [\"Marvelous\", \"Methodical\", \"Medalist\"],\n",
      "  \"last\": [\"Powerful\", \"Persevering\", \"Precise\"]\n",
      "}\n",
      "</athlete_name>\n"
     ]
    }
   ],
   "source": [
    "message = (\n",
    "    client.messages.create(\n",
    "        model=MODEL_NAME,\n",
    "        max_tokens=1024,\n",
    "        messages=[\n",
    "            {\n",
    "                \"role\": \"user\",\n",
    "                \"content\": \"\"\"Give me a JSON dict with the names of 5 famous athletes & their sports.\n",
    "Put this dictionary in <athlete_sports> tags.\n",
    "\n",
    "Then, for each athlete, output an additional JSON dictionary. In each of these additional dictionaries:\n",
    "- Include two keys: the athlete's first name and last name.\n",
    "- For the values, list three words that start with the same letter as that name.\n",
    "Put each of these additional dictionaries in separate <athlete_name> tags.\"\"\",\n",
    "            },\n",
    "            {\"role\": \"assistant\", \"content\": \"Here is the JSON requested:\"},\n",
    "        ],\n",
    "    )\n",
    "    .content[0]\n",
    "    .text\n",
    ")\n",
    "print(message)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74043369",
   "metadata": {},
   "source": [
    "Now, we can use an extraction regex to get all the dictionaries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "bd847a70",
   "metadata": {},
   "outputs": [],
   "source": [
    "import re\n",
    "\n",
    "\n",
    "def extract_between_tags(tag: str, string: str, strip: bool = False) -> list[str]:\n",
    "    ext_list = re.findall(f\"<{tag}>(.+?)</{tag}>\", string, re.DOTALL)\n",
    "    if strip:\n",
    "        ext_list = [e.strip() for e in ext_list]\n",
    "    return ext_list\n",
    "\n",
    "\n",
    "athlete_sports_dict = json.loads(extract_between_tags(\"athlete_sports\", message)[0])\n",
    "athlete_name_dicts = [json.loads(d) for d in extract_between_tags(\"athlete_name\", message)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "eb61ee06",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Lionel Messi': 'Soccer',\n",
      " 'Michael Jordan': 'Basketball',\n",
      " 'Michael Phelps': 'Swimming',\n",
      " 'Serena Williams': 'Tennis',\n",
      " 'Usain Bolt': 'Track and Field'}\n"
     ]
    }
   ],
   "source": [
    "pprint(athlete_sports_dict)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "57dade0f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'first': ['Magnificent',\n",
      "            'Motivating',\n",
      "            'Memorable'],\n",
      "  'last': ['Joyful',\n",
      "           'Jumping',\n",
      "           'Jocular']},\n",
      " {'first': ['Skillful',\n",
      "            'Strong',\n",
      "            'Superstar'],\n",
      "  'last': ['Winning',\n",
      "           'Willful',\n",
      "           'Wise']},\n",
      " {'first': ['Legendary',\n",
      "            'Lively',\n",
      "            'Leaping'],\n",
      "  'last': ['Magical',\n",
      "           'Marvelous',\n",
      "           'Masterful']},\n",
      " {'first': ['Unbeatable',\n",
      "            'Unbelievable',\n",
      "            'Unstoppable'],\n",
      "  'last': ['Brave',\n",
      "           'Bold',\n",
      "           'Brilliant']},\n",
      " {'first': ['Marvelous',\n",
      "            'Methodical',\n",
      "            'Medalist'],\n",
      "  'last': ['Powerful',\n",
      "           'Persevering',\n",
      "           'Precise']}]\n"
     ]
    }
   ],
   "source": [
    "pprint(athlete_name_dicts, width=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e5854c0",
   "metadata": {},
   "source": [
    "So to recap:\n",
    "\n",
    "- You can use string parsing to extract the text between \"```json\" and \"```\" to get the JSON.\n",
    "- You can remove preambles *before* the JSON via a partial Assistant message. (However, this removes the possibility of having Claude do \"Chain of Thought\" for increased intelligence before beginning to output the JSON.)\n",
    "- You can get rid of text that comes *after* the JSON by using a stop sequence.\n",
    "- You can instruct Claude to output JSON in XML tags to make it easy to collect afterward for more complex prompts."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
